home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Cream of the Crop 1
/
Cream of the Crop 1.iso
/
PROGRAM
/
SBDOS10.ARJ
/
SB_DRIVE.C
< prev
next >
Wrap
Text File
|
1992-06-25
|
41KB
|
1,353 lines
/*======================================================================
Device driver for the Creative Labs Sound Blaster card.
[ This file is part of the SBMSDOS v1.0 distibution ]
Michael Fulbright (msf@as.arizona.edu)
This file was originally distributed by the fellow below, I'm
just borrowing it.
=====================================================================
ORIGINAL HEADER FOLLOWS (msf)
=====================================================================
[ This file is a part of SBlast-BSD-1.4 ]
Steve Haehnichen <shaehnic@ucsd.edu>
$Id: sb_driver.c,v 1.29 1992/06/13 01:46:43 steve Exp steve $
Copyright (C) 1992 Steve Haehnichen.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* $Log: sb_driver.c,v $
* Revision 1.29 1992/06/13 01:46:43 steve
* Released in SBlast-BSD-1.4
======================================================================*/
/* MSF - Following not needed for MSDOS
#include <sys/user.h>
#include <sys/file.h>
*/
/* MSF - following needed in general */
#include <stdio.h>
#include <stdlib.h>
/* MSF - following needed for MSC specific calls */
#include <dos.h>
#include <bios.h>
#include "sblast.h" /* User-level structures and defs */
#include "sb_regs.h" /* Register and command values */
/*
* Defining DEBUG will print out lots of useful kernel activity
* information. If there are problems, it's the first thing you
* should do. You can also define FULL_DEBUG for more info than
* you probably want. (If you consider any DEBUG useless, just
* turn it into a FULL_DEBUG.)
*/
/* #define DEBUG */
/* #define FULL_DEBUG */
#if defined (FULL_DEBUG) && !defined (DEBUG)
# define DEBUG
#endif
/*
* Here's the debugging macro I use here and there, so I can turn
* them all on or off in one place.
*/
#ifdef DEBUG
# define DPRINTF(x) printf x
#else
# define DPRINTF(x)
#endif
/*
* This is the highest DSP speed that will use "Low-Speed" mode.
* Anything greater than this will use the "High-Speed" mode instead.
*/
#define MAX_LOW_SPEED 22222
/*
* This is the speed the DSP will start at when you reboot the system.
* If I can read the speed registers, then maybe I should just use
* the current card setting. Does it matter?
* (43478 is the closest we can get to 44 KHz)
*/
#define INITIAL_DSP_SPEED 8000
/*
* Define this if you consider ADC overruns to be an error, and
* want to abort the read() call. If you are making real recordings,
* this is usually the case, or you will have gaps in the sound.
* (This shouldn't happen. Most apps can keep up with 45K/sec.)
* For real-time use, overruns should be ignored.
* This looks like an ioctl-toggle candidate to me.
*/
#define ERROR_ON_OVERRUN
#define FALSE 0
#define TRUE 1
#define GOOD 1
#define FAIL 0
#define ON 1
#define OFF 0
/* Number of "cycles" in one second. Shouldn't this be somewhere else? */
#define HZ 100
#define TIMEOUT (10 * HZ) /* FM interrupt patience in clock ticks */
/* Semaphore return codes. This tells the reason for the wakeup(). */
#define WOKEN_BY_INTERRUPT 1
#define WOKEN_BY_TIMEOUT 2
#define DSP_LOOP_MAX 10000
#define DSP_UNIT 0
enum { PLAY, RECORD }; /* DSP sampling directions */
/*
* NOTE - MSF - Well, I cant afford OS/2 yet, so I'm stuck with
* wonderful 64k segments in MSC 5.1. Comment below
* holds, except I allocate 2 buffers in a 32k chunk.
* To be sure things will work DO NOT CHOOSE DSP_BUF_SIZE
* to be larger than 1/3 sizeof(memory_chunk)!.
*
* ORIGINAL COMMENT FOLLOWS (msf)
*
* This is the DSP memory buffer size. Choose wisely. :-)
* I started with 21K buffers, and then later changed to 64K buffers.
* This hogs more kernel memory, but gives fewer Pops and
* needs servicing less often. Adjust to taste.
* Note that it must be an EVEN number if you want stereo to work.
* (See dsp_find_buffers() for a better explanation
* Note that the same amount of memory (192K) is hogged, regardless of
* the buffer size you choose. This makes it easier to allocate page-safe
* buffers, but should probably be changed to attempt to pack more into
* one 64K page. Be really sure you understand what you are doing if
* you change the size of memory_chunk[].
* 64K is the biggest DSP_BUF_SIZE you can have.
* Smaller buffer sizes make aborts and small reads more responsive.
* For real-time stuff, perhaps ~10K would be better.
*/
#define DSP_BUF_SIZE (10000)
static char memory_chunk[32 * 1024]; /* Be careful! See above */
/* This structure will be instantiated as a single global holder
for all DSP-related variables and flags. */
/* MSF - added speaker_on flag to tell if speaker on or off */
struct sb_dsp_type
{
unsigned int speed; /* DSP sampling rate */
int timeout; /* Timeout for one DSP buffer */
BYTE compression; /* Current DAC decompression mode */
FLAG hispeed; /* 1 = High Speed DSP mode, 0 = low speed */
FLAG in_stereo; /* 1 = currently in stereo, 0 = mono */
BYTE start_command; /* current DSP start command */
int error; /* Current error status on read/write */
int semaphore; /* Silly place-holder int for dsp_dma_start */
void (*cont_xfer)(void); /* Function to call to continue a DMA xfer */
char *buf[2]; /* Two pointers to mono-page buffers */
long phys_buf[2]; /* Physical addresses for dsp buffers */
FLAG full[2]; /* True when that buffer is full */
int used[2]; /* Buffer bytes used by read/write */
BYTE active; /* The buffer currently engaged in DMA */
BYTE hi; /* The buffer being filled/emptied by user */
FLAG first_block; /* True for the first DAC block */
FLAG dont_block; /* FNDELAY flag sets this, clear otherwise */
FLAG speaker_on; /* added by MSF - TRUE - voice on, else off */
};
static struct sb_dsp_type dsp; /* 'dsp' structure used everywhere */
#define NUM_UNITS 1
/* This will be used as a global holder for the current Sound Blaster
general status values. */
struct sb_status_type
{
FLAG dsp_in_use; /* DSP or MIDI open for reading or writing */
FLAG fm_in_use; /* FM open for ioctls */
FLAG cms_in_use; /* CMS open for ioctls */
FLAG alive; /* Card present? */
unsigned int addr; /* Sound Blaster card address */
unsigned int irq; /* MSF-added this */
int *wake[NUM_UNITS]; /* What to wakeup on interrupt */
};
static struct sb_status_type status; /* Global current status */
/*
* Forward declarations galore!
*/
int sb_probe (struct sb_conf *dev);
int sb_attach (struct sb_conf *dev);
void sb_unattach (void);
void sb_sendb (unsigned select_addr, BYTE reg,
unsigned data_addr, BYTE value);
int dsp_reset (void);
int dsp_open (void);
int dsp_close (int flags);
unsigned int dsp_set_speed (unsigned int *speed);
int dsp_command (BYTE val);
int dsp_set_voice (int on);
int dsp_flush_dac (void);
int dsp_set_compression (int mode);
int dsp_set_stereo (FLAG on);
int dsp_write (BYTE far *ptr, int len);
int dsp_read (BYTE far *ptr, int len);
void dsp_find_buffers (void);
void dsp_dma_start (int dir);
void dsp_next_write (void);
void dsp_next_read (void);
void mixer_send (BYTE reg, BYTE val);
BYTE mixer_read_reg (BYTE reg);
int mixer_reset (void);
int mixer_set_levels (struct sb_mixer_levels *l);
int mixer_set_params (struct sb_mixer_params *p);
int mixer_get_levels (struct sb_mixer_levels *l);
int mixer_get_params (struct sb_mixer_params *params);
/* added by msf */
/* old_irqptr holds the old vector for the SB IRQ while we use it here.
* The IRQ vector is restored to this value when the program terminates */
void (_CDECL interrupt far * _CDECL old_irqptr)();
/* this is how on declares a function to be an interrupt handler under
* MSC 5.1. I know Borland compilers have their own way of handling this */
void cdecl interrupt far sb_intr (void);
void outb( int, int );
/* MSF - following implements my own 'sleep' functions.
* time_const contains the number of loops/s a for loop can do */
unsigned long time_const;
void init_timers(void);
/*
* Probing sets dev->dev_alive, status.alive, and returns 1 if the
* card is detected. Note that we are not using the "official" Adlib
* of checking for the presence of the card because it's tacky and
* takes too much mucking about. We just attempt to reset the DSP and
* assume that a DSP-READY signal means the card is there. If you
* have another card that fools this probe, I'd really like to hear
* about it. :-) Future versions may query the DSP version number
* instead.
*/
int
sb_probe (struct sb_conf *dev)
{
#ifdef FULL_DEBUG
printf ("sb: sb_probe() called.\n");
printf ("Dev.addr = %d, dev.irq= %d \n",dev->addr, dev->irq);
#endif
status.addr = (unsigned int) dev->addr;
if (dsp_reset () == GOOD)
status.alive = 1;
else
{
status.alive = 0;
printf ("sb: Sound Blaster Not found! Driver not installed.\n");
}
return (status.alive);
}
/* This is called by the kernel if probe() is successful. */
int
sb_attach (struct sb_conf *dev)
{
int i;
unsigned char im, tm;
unsigned long pp;
/* MSF - added irq field to status */
status.addr = (unsigned int) dev->addr;
status.irq = (unsigned int) dev->irq;
status.fm_in_use = 0;
status.dsp_in_use = 0;
status.addr = (unsigned int) dev->addr;
status.irq = (unsigned int) dev->irq;
DPRINTF(("status.addr,status.irq= %x %x\n",status.addr,status.irq));
DPRINTF(("dev.addr,dev.irq= %x %x\n",dev->addr,dev->irq));
dsp_find_buffers();
for (i = 0; i < NUM_UNITS; i++)
status.wake[i] = NULL;
/*
* These are default startup settings.
* I'm wondering if I should just leave the card in the same
* speed/stereo state that it is in. I decided to leave the mixer
* alone, and like that better. Ideas?
*/
dsp.compression = PCM_8;
dsp.speed = INITIAL_DSP_SPEED;
dsp_set_stereo (FALSE);
/* MSF - added this to set up some more things */
dsp_open();
/* MSF - calculate delay time constants for 'sleep' simulator */
init_timers();
/* MSF - added following to grab SB IRQ.
* If some of code looks familiar to you, thanks. I grabbed it off of
* blaster@porter.geo.brown.edu and cant find name of poster. */
/* now turn on the interrupt */
/* Enable interrupts on PIC */
im = inp(0x21);
tm = ~(1 << dev->irq);
outb(0x21,im & tm);
_enable();
outb(0x20,0x20);
DPRINTF(("Interrupt mask register = %d\n",im));
DPRINTF(("Setup IRQ %d with mask %d\n",dev->irq, tm));
/* Set up DSP interrupt */
pp = (unsigned long) _dos_getvect(8+dev->irq);
DPRINTF(("IRQ 7 was now %lu \n",pp));
/* grab old vector and store */
old_irqptr = _dos_getvect(8+dev->irq);
_dos_setvect(8+dev->irq, sb_intr);
_enable();
DPRINTF (("sb: Sound Blaster installed. (Port 0x%X, IRQ %d, DMA %d)\n",
status.addr, dev->irq, SB_DMA_CHAN));
/* MSF - set things up so if program exits it will call sb_unattach */
i=atexit( sb_unattach );
if (i!=0)
printf("Error calling atexit\n");
return (GOOD);
}
/* MSF - This should be called when done, resets interrupts and stuff
* Machine can act unpredictably otherwise. */
void
sb_unattach (void)
{
unsigned char tm;
DPRINTF(("In sb_unattach\n"));
/* see if any DMA is going on */
if (dsp.cont_xfer != NULL)
{
printf("Waiting for DMA to cease.\n");
tm=wait_for_intr(dsp.timeout);
if (tm==FAIL) printf("Couldnt get interrupt, unattaching anyway!\n");
}
/* Return DSP interrupt */
if ( old_irqptr != NULL )
{
DPRINTF(("Resetting vector back to original\n"));
_dos_setvect(8+status.irq, old_irqptr);
}
old_irqptr = NULL;
/* Mask DSP interrupt */
tm = inp(0x21);
outb(0x21,tm | (1 << status.irq));
}
/*
* MSF - If you are not using MSC 5.1 compat C compiler then following
* will need changes. See PORTING document.
*
* This gets called anytime there is an interrupt on the SB IRQ.
* Great effort is made to service DSP interrupts immediately
* to keep the buffers flowing and pops small. I wish the card
* had a bigger internal buffer so we didn't have to worry.
*/
void cdecl interrupt far
sb_intr (void)
{
int unit = 0;
/* MSF - probably dont want to call DOS right not */
/* DPRINTF (("sb_intr(): Got interrupt on unit %d\n", unit)); */
/*
* If the DSP is doing a transfer, and the interrupt was on the
* DSP unit (as opposed to, say, the FM unit), then hurry up and
* call the function to continue the DSP transfer.
*/
if (dsp.cont_xfer && unit == DSP_UNIT)
(*dsp.cont_xfer)();
/*
* If this Unit is expecting an interrupt, then set the semaphore
* and wake up the sleeper. Otherwise, we probably weren't expecting
* this interrupt, so just ignore it. This also happens when two
* interrupts are acked in a row, without sleeping in between.
* Not really a problem, but there should be a clean way to fix it.
*/
if (status.wake[unit] != NULL)
{
*status.wake[unit] = WOKEN_BY_INTERRUPT;
}
else
{
/* MSF - uncomment at own risk, may invoke DOS at a bad time */
/* DPRINTF (("sb_intr(): An ignored interrupt!\n")); */
}
/* reenable interupts via 8259 */
/* _disable, _enable turn maskable interrupts off/on */
_disable();
outb(0x20,0x20);
_enable();
return;
}
/* Following added by MSF to do timing which UNIX takes for granted! */
/* the following sets up constants for cpu independent timer routines */
void init_timers(void)
{
long i, start, end;
/* loop to 500000, which should take around a 1/2 sec or so */
/* _bios_timeofday returns # of cycles since midnight or something.
* there are 18.3 cycles/sec */
_bios_timeofday(_TIME_GETCLOCK, &start);
#pragma loop_opt(off)
for(i=0; i< 500000; i++);
#pragma loop_opt(on)
_bios_timeofday(_TIME_GETCLOCK, &end);
time_const = (500000L*183L)/(end-start)/10;
DPRINTF(("Time_const=%lu\n",time_const));
}
/* MSF - supposed to wait ten microseconds, although I havent timed it
* Probably waits longer, important thing is that you cant write
* to the DSP without a delay between writes */
void tenmicrosec(void)
{
int i,length;
length = ((unsigned long)time_const/100000L)+1;
#pragma loop_opt(off)
for (i=0; i<length; i++);
#pragma loop_opt(on)
}
/*
* Send one byte to the named Sound Blaster register.
* The SBDK recommends 3.3 microsecs after an address write,
* and 23 after a data write. What they don't tell you is that
* you also can't READ from the ports too soon, or crash! (Same timing?)
* Anyway, 10 usecs is as close as we can get, so..
*
* NOTE: This function is wicked. It ties up the entire machine for
* over forty microseconds. This is unacceptable, but I'm not sure how
* to make it better. Look for a re-write in future versions.
* Does 30 microsecs merit a full timeout() proceedure?
*/
void
sb_sendb (unsigned int select_addr, BYTE reg,
unsigned int data_addr, BYTE value)
{
outb (select_addr, reg);
tenmicrosec();
outb (data_addr, value);
tenmicrosec();
tenmicrosec();
tenmicrosec();
}
/*
* MSF - I had to really change this.
* Instead of calling the sleep() function in UNIX I just
* do a loop which is roughly calibrated to the correct time period
* If anyone has suggestions on how this can be done better let me
* know.
*
* Patience is ignored.
*
*/
int
wait_for_intr (int patience)
{
int sem = 0;
int result;
int unit = 0;
long i, delay;
DPRINTF (("Waiting for interrupt on unit %d.\n", unit));
status.wake[unit] = &sem;
sem=WOKEN_BY_TIMEOUT;
/* following designed to wait up to time it takes to fill buffer once */
delay = (DSP_BUF_SIZE*(time_const/dsp.speed));
#pragma loop_opt(off)
for (i=0; i<delay; i++) {
if (sem==WOKEN_BY_INTERRUPT) break;
}
#pragma loop_opt(on)
if (sem == WOKEN_BY_TIMEOUT)
{
printf ("sb: Interrupt time out.\n");
dsp.error = EIO;
result = FAIL;
}
else
{
DPRINTF (("Got it!\n"));
result = GOOD;
}
return (result);
}
/*
* DSP functions.
*/
/*
* Reset the DSP chip, and initialize all DSP variables back
* to square one. This can be done at any time to abort
* a transfer and break out of locked modes. (Like MIDI UART mode!)
* Note that resetting the DSP puts the speed back to 8196, but
* it shouldn't matter because we set the speed in dsp_open.
* Keep this in mind, though, if you use DSP_IOCTL_RESET from
* inside a program.
*/
int
dsp_reset (void)
{
int i, s;
DPRINTF (("Resetting DSP.\n"));
dsp.used[0] = dsp.used[1] = 0; /* This is only for write; see dsp_read() */
dsp.full[0] = dsp.full[1] = FALSE;
dsp.hi = dsp.active = 0;
dsp.first_block = 1;
dsp.error = ESUCCESS;
dsp.cont_xfer = NULL;
status.wake[DSP_UNIT] = NULL;
/*
* This is how you reset the DSP, according to the SBDK:
* Send 0x01 to DSP_RESET (0x226) and wait for three microseconds.
* Then send a 0x00 to the same port.
* Poll until DSP_RDAVAIL's most significant bit is set, indicating
* data ready, then read a byte from DSP_RDDATA. It should be 0xAA.
* Allow 100 microseconds for the reset.
*/
tenmicrosec(); /* Lets things settle down. (necessary?) */
outb (DSP_RESET, 0x01);
tenmicrosec();
outb (DSP_RESET, 0x00);
dsp.error = EIO;
for (i = DSP_LOOP_MAX; i; i--)
{
tenmicrosec();
if ((inp (DSP_RDAVAIL) & DSP_DATA_AVAIL)
&& ((inp (DSP_RDDATA) & 0xFF) == DSP_READY))
{
dsp.error = ESUCCESS;
break;
}
}
if (dsp.error != ESUCCESS)
return (FAIL);
else
return (GOOD);
}
/*
* MSF - following was changed to deal with 64k segments of MSC.
* Follows idea of original code, just looks uglier.
* I grab 32k and look for 2 10K buffers in that space which
* dont cross a 64k boundary.
*
* ORIGINAL COMMENTS (msf)
*
* This finds page-safe buffers for the DSP DMA to use. A single DMA
* transfer can never cross a 64K page boundary, so we have to get
* aligned buffers for DMA. The current method is wasteful, but
* allows any buffer size up to the full 64K (which is nice). We grab
* 3 * 64K in the static global memory_chunk, and find the first 64K
* alignment in it for the first buffer. The second buffer starts 64K
* later, at the next alignment. Yeah, it's gross, but it's flexible.
* I'm certainly open to ideas! (Using cool kernel memory alloc is tricky
* and not real portable.)
*/
void
dsp_find_buffers (void)
{
unsigned long startseg, startoff, startaddr, endaddr, break64k;
/* MSF rewrite */
/* find 64k boundaries */
startseg = (unsigned long) ((unsigned long)memory_chunk & 0xffff0000) >> 16;
startoff = (unsigned long) memory_chunk & 0x0000ffff;
startaddr = startseg*16 + startoff;
endaddr = startaddr + sizeof(memory_chunk);
break64k = (endaddr >> 16)<<16;
/* break64k holds addr where 64k boundary is within array is */
/* Three cases now
* 1) Break is outside memory_chunk
* 2) Break splits memory_chunk into two pieces, each which can be a buffer
* 3) " " " " " " , one of which can hold both
*/
if (break64k < startaddr)
{
dsp.buf[0] = memory_chunk;
dsp.buf[1] = memory_chunk + DSP_BUF_SIZE;
dsp.phys_buf[0] = startaddr;
dsp.phys_buf[1] = startaddr+DSP_BUF_SIZE;
}
else if ((break64k-startaddr) > DSP_BUF_SIZE)
{
/* enough room for first buffer */
dsp.buf[0] = memory_chunk;
dsp.phys_buf[0] = startaddr;
/* now see if break allows another immediately after first,
* or if we need to move it to 64k break */
if ( (break64k-startaddr-DSP_BUF_SIZE) > DSP_BUF_SIZE)
{
dsp.buf[1] = memory_chunk + DSP_BUF_SIZE;
dsp.phys_buf[1] = startaddr + DSP_BUF_SIZE;
}
else
{
dsp.buf[1] = memory_chunk + break64k-startaddr;
dsp.phys_buf[1] = break64k;
}
}
else
{
/* not enough room in first chunk, so both must fit in second */
dsp.buf[0] = memory_chunk + break64k-startaddr;
dsp.phys_buf[0] = break64k;
dsp.buf[1] = dsp.buf[0] + DSP_BUF_SIZE;
dsp.phys_buf[1] = dsp.phys_buf[0] + DSP_BUF_SIZE;
}
/* Whew! Hope that covers all cases */
}
/*
* Start a DMA transfer to/from the DSP.
* This one needs more explaining than I would like to put in comments,
* so look at the accompanying documentation, which, of course, hasn't
* been written yet. :-)
*
* This function takes one argument, 'dir' which is either PLAY or
* RECORD, and starts a DMA transfer of as many bytes as are in
* dsp.buf[dsp.active]. This allows for partial-buffers.
*
* Always call this with interrupts masked.
*/
void
dsp_dma_start (int dir)
{
unsigned int count = dsp.used[dsp.active] - 1;
/* Prepare the DMAC. See sb_regs for defs and more info. */
/* MSF - this is real stupid, but MSC optimization makes writes too fast
with inline code, so calling dummy function outb() slows it
down enough ! */
outb (DMA_MASK_REG, DMA_MASK);
outb (DMA_CLEAR, 0);
outb (DMA_MODE, (dir == RECORD) ? DMA_MODE_READ : DMA_MODE_WRITE);
outb (DMA_PAGE, (dsp.phys_buf[dsp.active] & 0xff0000) >> 16); /* Page */
outb (DMA_ADDRESS, dsp.phys_buf[dsp.active] & 0x00ff); /* LSB of address */
outb (DMA_ADDRESS, (dsp.phys_buf[dsp.active] & 0xff00) >> 8);
outb (DMA_COUNT, count & 0x00ff);
outb (DMA_COUNT, (count & 0xff00) >> 8);
outb (DMA_MASK_REG, DMA_UNMASK);
DPRINTF(("Sent DMA instructions for length %d\n",count));
/*
* The DMAC is ready, now send the commands to the DSP.
* Notice that there are two entirely different operations for
* Low and High speed DSP. With HS, You only have to send the
* byte-count when it changes, and that requires an extra command
* (Checking if it's a new size is quicker than always sending it.)
*/
if (dsp.hispeed)
{
DPRINTF (("Starting High-Speed DMA of %d bytes to/from buffer %d.\n",
dsp.used[dsp.active], dsp.active));
if (count != DSP_BUF_SIZE - 1)
{
dsp_command (HIGH_SPEED_SIZE);
dsp_command (count & 0x00ff);
dsp_command ((count & 0xff00) >> 8);
}
dsp_command (dsp.start_command); /* GO! */
}
else /* Low Speed transfer */
{
DPRINTF (("Starting Low-Speed DMA xfer of %d bytes to/from buffer %d.\n",
dsp.used[dsp.active], dsp.active));
dsp_command (dsp.start_command);
dsp_command (count & 0x00ff);
dsp_command ((count & 0xff00) >> 8); /* GO! */
}
/* This sets up the function to call at the next interrupt: */
dsp.cont_xfer = (dir == RECORD) ? dsp_next_read : dsp_next_write;
}
/*
* This is basically the interrupt handler for Playback DMA interrupts.
* Our first priority is to get the other buffer playing, and worry
* about the rest after.
*/
void
dsp_next_write (void)
{
inp (DSP_RDAVAIL); /* ack interrupt */
/* DPRINTF (("Got interrupt. DMA done on buffer %d.\n", dsp.active)); */
dsp.full[dsp.active] = FALSE; /* Just-played buffer is not full */
dsp.active ^= 1;
if (dsp.full[dsp.active])
{
/* DPRINTF (("Starting next buffer off..\n")); */
dsp_dma_start (PLAY);
}
else
{
/* DPRINTF (("Other buffer is not full. Clearing cont_xfer..\n")); */
dsp.cont_xfer = NULL;
}
}
/*
* This is similar to dsp_next_write(), but for Sampling instead of playback.
*/
void
dsp_next_read (void)
{
inp (DSP_RDAVAIL); /* ack interrupt */
/* The active buffer is currently full of samples */
dsp.full[dsp.active] = TRUE;
dsp.used[dsp.active] = 0;
/* Flop to the next buffer and fill it up too, unless it's already full */
dsp.active ^= 1;
if (dsp.full[dsp.active])
{
#ifdef ERROR_ON_OVERRUN
/*
* An overrun occurs when we fill up two buffers faster than the
* user is reading them. Lossage has to occur. This may or
* may not be bad. For recording, it's bad. For real-time
* FFTs and such, it's not a real big deal.
*/
dsp.error = ERANGE;
#endif
dsp.cont_xfer = NULL;
}
else
dsp_dma_start (RECORD);
}
/*
* This is the main recording function. Program flow is tricky with
* all the double-buffering and interrupts, but such are drivers.
* To summarize, it starts filling the first buffer when the user
* requests the first read(). (Filling on the open() call would be silly.)
* When one buffer is done filling, we fill the other one while copying
* the fresh data to the user.
* If the user doesn't read fast enough for that DSP speed, we have an
* overrun. See above concerning ERROR_ON_OVERRUN.
*/
int
dsp_read (BYTE far *ptr, int len)
{
unsigned int bytecount, hunk_size;
/* MSF - if speaker on turn it off */
if (dsp.speaker_on) dsp_set_voice(OFF);
if (dsp.first_block)
{
DPRINTF (("Kicking in first_block DSP read..\n"));
dsp.first_block = FALSE;
dsp.start_command = dsp.hispeed ? HS_ADC_8 : ADC_8;
dsp.used[0] = dsp.used[1] = DSP_BUF_SIZE; /* Start with both empty */
dsp_dma_start (RECORD);
}
/* just read in len bytes into buffer pointed too by ptr */
bytecount = 0;
/* While there is still data to read, and data in this chunk.. */
while (bytecount < len)
{
if (dsp.error != ESUCCESS)
return (dsp.error);
while (dsp.full[dsp.hi] == FALSE)
{
DPRINTF (("Waiting for buffer %d to fill..\n", dsp.hi));
wait_for_intr (dsp.timeout);
}
/* Now we give a piece of the buffer to the user */
hunk_size = min (len - bytecount,
DSP_BUF_SIZE - dsp.used[dsp.hi]);
DPRINTF (("Copying %d bytes from buffer %d.\n", hunk_size, dsp.hi));
memmove( ptr, dsp.buf[dsp.hi]+dsp.used[dsp.hi], hunk_size);
dsp.used[dsp.hi] += hunk_size;
if (dsp.used[dsp.hi] == DSP_BUF_SIZE)
{
DPRINTF (("Drained all of buffer %d.\n", dsp.hi));
dsp.full[dsp.hi] = FALSE;
dsp.hi ^= 1;
dsp.used[dsp.hi] = 0;
}
bytecount += hunk_size;
} /* While there are bytes left in chunk */
return (dsp.error);
}
/*
* Main function for DSP sampling.
* No such thing as an overrun here, but if the user writes too
* slowly, we have to restart the DSP buffer ping-pong manually.
* There will then be gaps, of course.
*/
/* rewrite by MSF for MSC */
int
dsp_write (BYTE far *ptr, int len)
{
unsigned int bytecount, hunk_size;
/*
* If this is the first write() operation for this open(),
* then figure out the DSP command to use.
* Have to check if it is High Speed, or one of the compressed modes.
* If this is the first block of a Compressed sound file,
* then we have to set the "Reference Bit" in the dsp.command for the
* first block transfer.
*/
/* MSF - if speaker off turn it on */
if (!dsp.speaker_on) dsp_set_voice(ON);
if (dsp.first_block)
{
dsp.first_block = FALSE;
if (dsp.hispeed)
dsp.start_command = HS_DAC_8;
else
{
dsp.start_command = dsp.compression;
if (dsp.compression == ADPCM_4
|| dsp.compression == ADPCM_2_6
|| dsp.compression == ADPCM_2)
dsp.start_command |= 1;
}
}
bytecount = 0;
/* While there is still data to write, and data in this chunk.. */
while (bytecount < len)
{
if (dsp.error != ESUCCESS) /* Shouldn't happen */
return (dsp.error);
hunk_size = min (len - bytecount,
DSP_BUF_SIZE - dsp.used[dsp.hi]);
DPRINTF (("Adding %d bytes (%d) to buffer %d.\n",
hunk_size, dsp.used[dsp.hi], dsp.hi));
memmove(dsp.buf[dsp.hi]+dsp.used[dsp.hi], ptr, hunk_size);
dsp.used[dsp.hi] += hunk_size;
if (dsp.used[dsp.hi] == DSP_BUF_SIZE)
{
dsp.full[dsp.hi] = TRUE;
DPRINTF (("Just finished filling buffer %d.\n", dsp.hi));
/*
* This is true when there are no DMA's currently
* active. This is either the first block, or the
* user is slow about writing. Start the chain reaction.
*/
if (dsp.cont_xfer == NULL)
{
DPRINTF (("Jump-Starting a fresh DMA...\n"));
dsp.active = dsp.hi;
dsp_dma_start (PLAY);
if (!dsp.hispeed)
dsp.start_command &= ~1; /* Clear reference bit */
status.wake[DSP_UNIT] = &dsp.semaphore;
}
/* If the OTHER buffer is playing, wait for it to finish. */
if (dsp.active == dsp.hi ^ 1)
{
DPRINTF (("Waiting for buffer %d to empty.\n", dsp.active));
wait_for_intr (dsp.timeout);
}
dsp.hi ^= 1; /* Switch to other buffer */
dsp.used[dsp.hi] = 0; /* Mark it as empty */
DPRINTF (("Other buffer (%d) is empty, continuing..\n", dsp.hi));
} /* if filled hi buffer */
bytecount += hunk_size;
} /* While there are bytes left in chunk */
return (dsp.error);
}
/*
* MSF - Call this if you want to make sure everything got written out.
*
* Play any bytes in the last waiting write() buffer and wait
* for all buffers to finish transferring.
* An even number of bytes is forced to keep the stereo channels
* straight. I don't think you'll miss one sample.
*/
int
dsp_flush_dac (void)
{
DPRINTF (("Flushing last buffer(s).\n"));
if (dsp.used[dsp.hi] != 0)
{
DPRINTF (("Playing the last %d bytes.\n", dsp.used[dsp.hi]));
if (dsp.in_stereo)
dsp.used[dsp.hi] &= ~1; /* Have to have even number of bytes. */
dsp.full[dsp.hi] = TRUE;
if (dsp.cont_xfer == NULL)
{
dsp.active = dsp.hi;
dsp_dma_start (PLAY);
}
}
/* Now wait for any transfers to finish up. */
while (dsp.cont_xfer)
{
DPRINTF (("Waiting for last DMA(s) to finish.\n"));
wait_for_intr (dsp.timeout);
}
return (ESUCCESS);
}
int
dsp_open (void)
{
#ifdef FULL_DEBUG
int i;
#endif
status.dsp_in_use = TRUE;
dsp_reset (); /* Resets card and inits variables */
dsp_set_speed (&dsp.speed); /* Set SB back to the current speed. */
dsp_set_voice(SPEAKER_ON); /* MSF - turn on speaker by default */
/*
* In case we do any High-Speed transfers, this sets the transfer
* size. We only need to set it when it changes. See dsp_dma_start()
*/
dsp_command (HIGH_SPEED_SIZE);
dsp_command ((DSP_BUF_SIZE - 1) & 0x00ff);
dsp_command (((DSP_BUF_SIZE - 1) & 0xff00) >> 8);
#ifdef FULL_DEBUG
/* Stuff buffers with loud garbage so we can hear/see leaks. */
for (i = 0; i < DSP_BUF_SIZE; i++)
dsp.buf[0][i] = dsp.buf[1][i] = i & 0xff;
#endif
return (ESUCCESS);
}
/* MSF - I dont think this is really needed. Just left it in just in case */
int
dsp_close (int flags)
{
if (status.dsp_in_use)
{
/* Wait for any last write buffers to empty */
dsp_flush_dac ();
dsp_reset ();
status.dsp_in_use = FALSE;
return (ESUCCESS);
}
else
return (ESRCH); /* Does this ever happen? */
}
/*
* Set the playback/recording speed of the DSP.
* This takes a pointer to an integer between DSP_MIN_SPEED
* and DSP_MAX_SPEED and changes that value to the actual speed
* you got. (Since the speed is so darn granular.)
* This also sets the dsp.hispeed flag appropriately.
* Note that Hi-Speed and compression are mutually exclusive!
* I also don't check all the different range limits that
* compression imposes. Supposedly, the DSP can't play compressed
* data as fast, but that's your problem. It will just run slower.
* Hmmm.. that could cause interrupt timeouts, I suppose.
*/
/* Changed by MSF to use unsigned int, still pass -1 (65535) to query speed
*/
unsigned int dsp_set_speed (unsigned int *speed) {
BYTE time_constant;
if (*speed == -1)
{
*speed = dsp.speed;
return (ESUCCESS);
}
if (*speed < DSP_MIN_SPEED || *speed > DSP_MAX_SPEED)
{
DPRINTF (("Attempt to set invalid speed (%ud)\n", *speed));
return (EINVAL);
}
if (*speed > MAX_LOW_SPEED)
{
if (dsp.compression != PCM_8)
return (EINVAL);
DPRINTF (("Using HiSpeed mode.\n"));
time_constant = (BYTE) ((65536L - (256000000L / (long)*speed)) >> 8);
dsp.speed = (256000000L / (65536L - ((long)time_constant << 8)));
dsp.hispeed = TRUE;
}
else
{
DPRINTF (("Using LowSpeed mode.\n"));
time_constant = (BYTE) (256 - (1000000 / *speed));
dsp.speed = 1000000 / (256 - time_constant);
dsp.hispeed = FALSE;
}
/* Here is where we actually set the card's speed */
if (dsp_command (SET_TIME_CONSTANT) == FAIL
|| dsp_command (time_constant) == FAIL)
return (EIO);
/*
* Replace speed with the speed we actually got.
* Set the DSP timeout to be twice as long as a full
* buffer should take.
*/
*speed = dsp.speed;
dsp.timeout = DSP_BUF_SIZE * HZ * 2 / dsp.speed;
DPRINTF (("Speed set to %d.\n", dsp.speed));
return (ESUCCESS);
}
/*
* Turn the DSP output speaker on and off.
* Argument of zero turns it off, on otherwise
*/
int
dsp_set_voice (int on)
{
if (dsp_command (on ? SPEAKER_ON : SPEAKER_OFF) == GOOD)
{
dsp.speaker_on = on ? TRUE : FALSE;
return (ESUCCESS);
}
else
return (EIO);
}
/*
* Set the DAC hardware decompression mode.
* No compression allowed for Hi-Speed mode, of course.
*/
int
dsp_set_compression (int mode)
{
switch (mode)
{
case ADPCM_4:
case ADPCM_2_6:
case ADPCM_2:
if (dsp.hispeed)
return (EINVAL); /* Fall through.. */
case PCM_8:
dsp.compression = mode;
return (ESUCCESS);
default:
return (EINVAL);
}
}
/*
* Send a command byte to the DSP port.
* First poll the DSP_STATUS port until the BUSY bit clears,
* then send the byte to the DSP_COMMAND port.
*/
int
dsp_command (BYTE val)
{
int i;
#ifdef FULL_DEBUG
printf ("Sending DSP command 0x%X\n", val);
#endif
for (i = DSP_LOOP_MAX; i; i--)
{
if ((inp (DSP_STATUS) & DSP_BUSY) == 0)
{
outb(DSP_COMMAND, val);
return (GOOD);
}
tenmicrosec ();
}
printf ("sb: dsp_command (%2X) failed!\n", val);
return (FAIL);
}
/*
* This turns stereo playback/recording on and off.
* For playback, it seems to only be a bit in the mixer.
* I don't know the secret to stereo sampling, so this may
* need reworking. If YOU know how to sample in stereo, send Email!
* Maybe this should be a Mixer parameter, but I really don't think so.
* It's a DSP Thing, isn't it? Hmm..
*/
int
dsp_set_stereo (FLAG select)
{
dsp.in_stereo = !!select;
/* Have to preserve DNFI bit from OUT_FILTER */
mixer_send (CHANNELS, ((mixer_read_reg (OUT_FILTER) & ~STEREO_DAC)
| (dsp.in_stereo ? STEREO_DAC : MONO_DAC)));
return (ESUCCESS);
}
/*
* Sets mixer volume levels.
* All levels except mic are 0 to 15, mic is 7. See sbinfo.doc
* for details on granularity and such.
* Basically, the mixer forces the lowest bit high, effectively
* reducing the possible settings by one half. Yes, that's right,
* volume levels have 8 settings, and microphone has four. Sucks.
*/
int
mixer_set_levels (struct sb_mixer_levels *l)
{
if (l->master.l & ~0xF || l->master.r & ~0xF
|| l->line.l & ~0xF || l->line.r & ~0xF
|| l->voc.l & ~0xF || l->voc.r & ~0xF
|| l->fm.l & ~0xF || l->fm.r & ~0xF
|| l->cd & ~0xF
|| l->mic & ~0x7)
return (EINVAL);
mixer_send (VOL_MASTER, (l->master.l << 4) | l->master.r);
mixer_send (VOL_LINE, (l->line.l << 4) | l->line.r);
mixer_send (VOL_VOC, (l->voc.l << 4) | l->voc.r);
mixer_send (VOL_FM, (l->fm.l << 4) | l->fm.r);
mixer_send (VOL_CD, l->cd);
mixer_send (VOL_MIC, l->mic);
return (ESUCCESS);
}
/*
* This sets aspects of the Mixer that are not volume levels.
* (Recording source, filter level, I/O filtering, and stereo.)
*/
int
mixer_set_params (struct sb_mixer_params *p)
{
if (p->record_source != SRC_MIC
&& p->record_source != SRC_CD
&& p->record_source != SRC_LINE)
return (EINVAL);
/*
* I'm not sure if this is The Right Thing. Should stereo
* be entirely under control of DSP? I like being able to toggle
* it while a sound is playing, so I do this... because I can.
*/
dsp.in_stereo = !!p->dsp_stereo;
mixer_send (RECORD_SRC, (p->record_source
| (p->hifreq_filter ? FREQ_HI : FREQ_LOW)
| (p->filter_input ? FILT_ON : FILT_OFF)));
mixer_send (OUT_FILTER, ((dsp.in_stereo ? STEREO_DAC : MONO_DAC)
| (p->filter_output ? FILT_ON : FILT_OFF)));
return (ESUCCESS);
}
/*
* Read the current mixer level settings into the user's struct.
*/
int
mixer_get_levels (struct sb_mixer_levels *l)
{
BYTE val;
val = mixer_read_reg (VOL_MASTER); /* Master */
l->master.l = B4(val >> 4);
l->master.r = B4(val);
val = mixer_read_reg (VOL_LINE); /* FM */
l->line.l = B4(val >> 4);
l->line.r = B4(val);
val = mixer_read_reg (VOL_VOC); /* DAC */
l->voc.l = B4(val >> 4);
l->voc.r = B4(val);
val = mixer_read_reg (VOL_FM); /* FM */
l->fm.l = B4(val >> 4);
l->fm.r = B4(val);
val = mixer_read_reg (VOL_CD); /* CD */
l->cd = B4(val);
val = mixer_read_reg (VOL_MIC); /* Microphone */
l->mic = B3(val);
return (ESUCCESS);
}
/*
* Read the current mixer parameters into the user's struct.
*/
int
mixer_get_params (struct sb_mixer_params *params)
{
BYTE val;
val = mixer_read_reg (RECORD_SRC);
params->record_source = val & 0x07;
params->hifreq_filter = !!(val & FREQ_HI);
params->filter_input = (val & FILT_OFF) ? OFF : ON;
params->filter_output = (mixer_read_reg (OUT_FILTER) & FILT_OFF) ? OFF : ON;
params->dsp_stereo = dsp.in_stereo;
return (ESUCCESS);
}
/*
* This is supposed to reset the mixer.
* Technically, a reset is performed by sending a byte to the MIXER_RESET
* register, but I don't like the default power-up settings, so I use
* these. Adjust to taste, and you have your own personalized mixer_reset
* ioctl.
*/
int
mixer_reset (void)
{
struct sb_mixer_levels l;
struct sb_mixer_params p;
p.filter_input = OFF;
p.filter_output = OFF;
p.hifreq_filter = TRUE;
p.record_source = SRC_LINE;
l.cd = 1;
l.mic = 1;
l.master.l = l.master.r = 11;
l.line.l = l.line.r = 15;
l.fm.l = l.fm.r = 15;
l.voc.l = l.voc.r = 15;
if (mixer_set_levels (&l) == ESUCCESS
&& mixer_set_params (&p) == ESUCCESS)
return (ESUCCESS);
else
return (EIO);
}
/*
* Send a byte 'val' to the Mixer register 'reg'.
*/
void
mixer_send (BYTE reg, BYTE val)
{
#if FULL_DEBUG
printf ("%02x: %02x\n", reg, val);
#endif
sb_sendb (MIXER_ADDR, reg, MIXER_DATA, val);
}
/*
* Returns the contents of the mixer register 'reg'.
*/
BYTE
mixer_read_reg (BYTE reg)
{
outb (MIXER_ADDR, reg);
tenmicrosec(); /* To make sure nobody reads too soon */
return (inp (MIXER_DATA));
}
/* MSF - added because I was sending stuff to the DMA registers too
* fast when MSC put in inline out dx,ax commands. This subroutine
* slows things down, maybe too much. Suggestions welcome. */
void outb(int x, int y)
{
outp(x,y);
}